﻿/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

using System.Drawing;
using System.Windows.Forms;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Daidalos.Management;
using Daidalos.Exception;
using Daidalos.Input;

namespace Daidalos.Graphics
{

    /// <summary>
    ///     The render window encapsules the window object of the OS' 
    ///     window manager.
    /// </summary>
    public sealed class RenderWindow : Form
    {

        #region Attributes
        /// <summary>
        ///     A flag indicating the present of a usable stencil
        ///     buffer.
        /// </summary>
        private bool hasStencilBuffer = false;

        /// <summary>
        ///     The width of the window.
        /// </summary>
        private int width = 800;

        /// <summary>
        ///     The height of the window.
        /// </summary>
        private int height = 600;

        /// <summary>
        ///     Syncronize rendering and screen refresh.
        /// </summary>
        private bool vsync = false;
        #endregion

        #region Properties
        /// <summary>
        ///     Defines the accessor to the VSync-flag.
        /// </summary>
        public bool VSync
        {
            get
            {
                return vsync;
            }
            set
            {
                vsync = value;
            }
        }

        /// <summary>
        ///     Implements a selector for the stencil buffer
        ///     flag.
        /// </summary>
        public bool HasStencilBuffer
        {
            get
            {
                return hasStencilBuffer;
            }
        }

        /// <summary>
        ///     Defines the accessor to the window's width.
        /// </summary>
        new public int Width
        {
            get
            {
                return width;
            }
            set
            {
                width = (value < 0) ? 0 : value;
            }
        }

        /// <summary>
        ///     Read/Write the window's height
        ///     Define the accessor to the window's height.
        /// </summary>
        new public int Height
        {
            get
            {
                return height;
            }
            set
            {
                height = (value < 0) ? 0 : value;
            }
        }

        /// <summary>
        ///    Implements a selector to the width of the
        ///    client area used for drawing.
        /// </summary>
        public int ClientAreaWidth
        {
            get
            {
                return ClientRectangle.Width;
            }
        }

        /// <summary>
        ///    Implements a selector to the height of the
        ///    client area used for drawing.
        /// </summary>
        public int ClientAreaHeight
        {
            get
            {
                return ClientRectangle.Height;
            }
        }
        #endregion

        #region Methods
        /// <summary>
        ///     This method creates a rendering window using the OS' window manager. The supplied
        ///     parameters are used to setup the window behaviour.
        /// </summary>
        /// <param name="width">
        ///     The desired window width.
        /// </param>
        /// <param name="height">
        ///     The desired window height.
        /// </param>
        /// <param name="fullscreen">
        ///     Is fullscreen mode desired?
        /// </param>
        /// <param name="title">
        ///     The desired window title.
        /// </param>
        public void CreateWindow(int width, int height, bool fullscreen, string title)
        {
            // Save the dimensions.
            Width = width;
            Height = height;

            // Set the client size.
            ClientSize = new Size(Width,Height);
            
            // Set the window title.
            Text = title;

            // Try to setup Direct3D.
            try
            {
                // Fetch the current display mode.
                DisplayMode displayMode = Manager.Adapters[Manager.Adapters.Default.Adapter].CurrentDisplayMode;

                // Create and setup the present parameters.
                PresentParameters presentParameters = new PresentParameters();

                // Windowed mode?
                presentParameters.Windowed = !fullscreen;

                // Select fragment format.
                LogManager.Instance.Log("  + Selecting fragment format.");
                Format fragmentFormat = Manager.Adapters[0].CurrentDisplayMode.Format;

                // Disable VSync.
                LogManager.Instance.Log("  + Disabling VSync.");
                presentParameters.PresentationInterval = PresentInterval.Immediate;

                // Use the depth- and stencil buffers.
                presentParameters.EnableAutoDepthStencil = true;
                presentParameters.AutoDepthStencilFormat = DepthFormat.D24S8;

                // Setup the backbuffer.
                LogManager.Instance.Log("  + Setting up backbuffers.");
                if (fullscreen)
                {
                    presentParameters.BackBufferFormat = displayMode.Format;
                    presentParameters.BackBufferWidth = width;
                    presentParameters.BackBufferHeight = height;
                }
                else
                {
                    presentParameters.BackBufferCount = 1;
                    presentParameters.BackBufferWidth = Width;
                    presentParameters.BackBufferHeight = Height;
                    presentParameters.BackBufferFormat = fragmentFormat;
                }
                presentParameters.SwapEffect = SwapEffect.Discard;

                // Disable multisampling.
                LogManager.Instance.Log("  + Disabling multi-sampling.");
                presentParameters.MultiSample = MultiSampleType.None;

                // Check the capabilities of the display adapter.
                Caps adapterCaps = Manager.GetDeviceCaps(0, DeviceType.Hardware);
                CreateFlags cFlags;
                if (adapterCaps.DeviceCaps.SupportsHardwareTransformAndLight)
                {
                    cFlags = CreateFlags.HardwareVertexProcessing;
                }
                else
                {
                    cFlags = CreateFlags.SoftwareVertexProcessing;
                }
                if (adapterCaps.DeviceCaps.SupportsPureDevice)
                {
                    cFlags |= CreateFlags.PureDevice;
                }

                // Now try to create a rendering device.
                RenderDevice.Instance.Device = new Device(0, DeviceType.Hardware, this, cFlags, presentParameters);

                // Show the window.
                Show();
            }
            catch(DirectXException e)
            {
                MessageBox.Show(e.Message + "\n" + e.StackTrace);
                // Something went wrong.
                throw new GenericException("Error while initializing render-window!");
            }

            // Register with input manager.
            LogManager.Instance.Log("Initializing Peripherals.");
            try
            {
                // Initialize input peripherals.
                Peripherals.Instance.InitializePeripherals(this);
            }
            catch (DirectXException)
            {
                // Pass on the exception, using the internal exception system.
                throw new GenericException("Failed to acquire input peripherals!");
            }
        }

        /// <summary>
        ///     The system call to repaint the window is passed to the engine's rendering
        ///     call.
        /// </summary>
        /// <param name="args">
        ///     Some arguments, that do not matter for us.
        /// </param>
        override protected void OnPaint(PaintEventArgs args)
        {
            // The window becomes valid, after being invalid before, so we need to repaint it's content
            // by rendering the scene again.
            RenderDevice.Instance.Render();
        }

        /// <summary>
        ///    This listener reacts on the resize of the application's window, which
        ///    often results in various DirectX resources becoming invalid.
        /// </summary>
        /// <param name="e">
        ///    Parameters passed to the application from the OS' window management.
        /// </param>
        override protected void OnResize(System.EventArgs e)
        {
            // Log the venture.
            LogManager.Instance.Log("Resizing window to " + this.Size.Width + "x" + this.Size.Height);

            // Update renderer's state.
            Width = this.Size.Width;
            Height = this.Size.Height;

            // Reload any lost textures.
			Management.ResourceManager.Instance.ReloadTextures();

            // Pass the call upwards.
			base.OnResize(e);
		}

        protected override void OnKeyPress(KeyPressEventArgs e)
        {
            if (e.KeyChar == 9)
            {
                Management.GraphicalConsole.Instance.Show();
                return;
            }
            if (Management.GraphicalConsole.Instance.Active)
                Management.GraphicalConsole.Instance.PassKey(e.KeyChar);
        }
        #endregion

    }

}
